/**
 * https://www.tslang.cn/docs/handbook/functions.html
 * 函数
 * 
 * 函数时JavaScript应用程序的基础。
 * 它帮助你实现抽象层，模拟类，信息隐藏和模块。
 * 
 */

// Named function
function add101(x,y){
    return x + y;
}

// Anonymous function
let myAdd101 = function(x, y) {return x + y; };

let z = 100;

function addToZ101(x, y){
    return x + y + z;
}

function add102(x: number, y: number): number {
    return x + y;
}

let myAdd102 = function(x: number, y:number): number{ return x + y; };

/**
 * 书写完整函数类型
 */

/**
 * 函数类型包含两部分：参数类型和返回值类型。
 * 当书写完整函数类型的时候，这两部分都是需要的。
 * 
 * @param x 
 * @param y 
 */
let myAdd103: (x: number, y: number) => number = function(x: number, y: number): number{
    return x + y;
};

/**
 * 只要参数类型是匹配的，那么就认为它是有效的函数类型，而不在乎参数名是否正确。
 * 
 * 第二部分是返回值类型。
 * 对于返回值，我们在函数和返回值类型之前使用（`=>`）符号，使之清晰明了。
 * 如之前提到的，返回值类型是函数类型的必要部分，如果函数没有返回任何值，
 * 你也必须指定返回值类型为 `void` 而不能留空。
 * 
 * 函数的类型只是由参数类型和返回值组成的。
 * 函数中使用的捕获变量不会体现在类型里。
 * 实际上，这些变量是函数的隐藏状态并不是组成API的一部分。
 * 
 * @param x 
 * @param y 
 */
let myAdd104: (baseValue: number, increment: number) => number = 
    function(x: number, y: number): number {return x + y; };

// myAdd105 has the full function type
let myAdd105 = function(x: number, y: number): number { return x + y;};

/**
 * 这叫做“按上下文归类”，是类型推断的一种。它帮助我们更好地为程序指定类型。
 */
// The parameters `x` and `y` have the type number
let myAdd106: (baseValue: number, increment: number) => number = 
    function(x,y) {return x + y; };

/**
 * 可选参数和默认参数
 * 
 * TypeScript里的每个函数参数都是必须的。
 * 这不是指不能传递 `null` 或 `undefined` 作为参数，而是说编译器检查用户
 * 是否为每个参数都传入了值。
 * 编译器还会假设只有这些参数会被传递进函数。
 * 
 * 简短的说，传递给一个函数的参数个数必须与函数期望的参数个数一致。
 */

function buildName101(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

// let result1 = buildName101("Bob"); // error, too few parameters
// let result2 = buildName101("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName101("Bob", "Adams");

/**
 * 在TypeScript里我们可以在参数名旁使用 `?` 实现可选参数的功能。
 * 
 * 可选参数必须跟在必须参数后面。
 * 如果我们想让`firstName`是可选的，那么就必须调整它们的位置，
 * 把`firstName`放在后面。
 * 
 * @param firstName 
 * @param lastName 
 */
function buildName102(firstName: string, lastName?: string) {
    if(lastName){
        return firstName + " " + lastName;
    }else{
        return firstName;
    }
}

let result21 = buildName102("Bob"); // works correctly now
// let result22 = buildName102("Bob", "Adams", "Sr."); // error, too many parameters
let result23 = buildName102("Bob", "Adams");

/**
 * 在所有必须参数后面的带默认初始化的参数都是可选的，
 * 与可选参数一样，在调用函数的时候可以省略。
 * 也就是说可选参数与末尾的默认参数共享参数类型。
 * 
 * `(firstName: string, lastName?: string) => string`
 * 
 * 与普通可选参数不同的是，带默认值的参数不需要放在必须参数的后面。
 * 如果带默认值的参数出现在必须参数前面，用户必须明确的传入 `undefined` 值来获得默认值。
 * 
 * @param firstName 
 * @param lastName 
 */
function buildName103(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}
let result31 = buildName103("Bob");                  // works correctly now, returns "Bob Smith"
let result32 = buildName103("Bob", undefined);       // still works, also returns "Bob Smith"
// let result33 = buildName103("Bob", "Adams", "Sr.");  // error, too many parameters
let result34 = buildName103("Bob", "Adams");     

/**
 * 剩余参数
 * 
 * 必要参数，默认参数和可选参数有个共同点：它们表示某一个参数。
 * 有时，你想同时操作多个参数，或者你并不知道会有多少参数传递进来。
 * 在JavaScript里，你可以使用 `arguments` 来访问所有传入的参数。
 * 
 * 在TypeScript里，你可以把所有参数收集到一个变量里。
 * 
 * 
 */

function buildName104(firstName:string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName104("Joseph", "Samuel", "Lucas", "MacKinzie");

/**
 * 剩余参数会被当做个数不限的可选参数。可以一个都没有，同样也可以有任意个。
 * 编译器创建参数数组，名字是你在省略号（...）后面给定的名字，你可以在函数体内使用这个数组。
 * 
 * 这个省略号也会在带有剩余参数的函数类型定义上使用到
 * 
 * @param firstName 
 * @param restOfName 
 */
function buildName105(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let buildNameFun101: (fname: string, ...rest: string[]) => string = buildName105;

/**
 * this
 * 
 * 学习如何在JavaScript里正确使用`this`就好比一场成年礼。
 * 由于TypeScript是JavaScript的超集，Typescript程序员也需要弄清 `this` 工作机制
 * 并且当有bug的时候能够找出错误所在。
 * 幸运的是，TypeScript能通知你错误地使用了`this`的地方。
 * 
 * 
 * 
 */


let deck101 = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function(){
        return function(){
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck101.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);


interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function (this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
        }
    }
};

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}
// class Handler {
//     info: string;
//     onClickBad(this: Handler, e: Event) {
//         // oops, used this here. using this callback would crash at runtime
//         this.info = e.message;
//     }
// }

